/**
 * Copyright 2012 ibiblio
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ibiblio.terasaur.bt

import java.util.Map
import org.ibiblio.terasaur.bencode.Bencode
import org.springframework.web.multipart.MultipartFile

class TorrentFileManager {
    private static _bencode = new Bencode()
    private String _rootPath
    public static TORRENT_FILE_EXTENSION = '.torrent'

    /**
     * Number of directory levels used when autogenerating path for
     * storing torrent files.
     */
    private static _INFO_HASH_PATH_LEVELS = 4

    public TorrentFileManager() {
    }

    public TorrentFileManager(String rootPath) {
        this._rootPath = this._calculateRootPath(rootPath)
    }

    private String _calculateRootPath(rootPath) {
        if (rootPath[-1] == File.separator) {
            return rootPath
        } else {
            return rootPath + File.separator
        }
    }

    // read file into classes
    public TorrentMetainfo getTorrentMetainfoForFile(String filePath) {
        def data = this._getTorrentData(filePath)
        def metainfo = new TorrentMetainfo(data)
        return metainfo
    }

    public TorrentMetainfo getTorrentMetainfoForInfoHash(String infoHash) {
        def path = this._getFilePathForInfoHash(infoHash)
        return this.getTorrentMetainfoForFile(path)
    }

    public TorrentMetainfo getTorrentMetainfoForInputStream(InputStream input, boolean keepPieces=false) {
        def data = this._getTorrentData(input)
        def metainfo = new TorrentMetainfo(data, keepPieces)
        return metainfo
    }

    public File getFileForInfoHash(String infoHash) {
        String path = this._getFilePathForInfoHash(infoHash)
        File f = new File(path)
        if (!f.exists()) {
            throw new FileNotFoundException(path)
        }
        return f
    }

    private String _getFilePathForInfoHash(String infoHash) {
        String path = this._getDirPathForInfoHash(infoHash) + File.separator + infoHash + TORRENT_FILE_EXTENSION
        return path
    }

    private String _getDirPathForInfoHash(String infoHash) {
        String path = this._rootPath + this._getTorrentSubdirForInfoHash(infoHash)
        return path
    }

    private String _getTorrentSubdirForInfoHash(String infoHash) {
        List pathList = []
        for (def i = 0; i < _INFO_HASH_PATH_LEVELS; ++i) {
            pathList << infoHash[i]
        }
        return pathList.join(File.separator)
    }

    private Map _getTorrentData(String filePath) {
        def input = new BufferedInputStream(new FileInputStream(filePath))
        return this._getTorrentData(input)
    }

    private Map _getTorrentData(InputStream input) {
        def torrentData = _bencode.decode(input)
        return torrentData
    }

    /**
     * Save an uploaded torrent file into the torrent tree.  Extracts data
     * from file data (i.e. info hash) to determine destination path.  Note
     * that this may not be the most efficient process since the file data
     * will have already been parsed.  However, this encapsulation should
     * reduce the chance of bugs.
     *
     * @param MultipartFile file
     */
    public void save(MultipartFile file) {
        BufferedInputStream input = new BufferedInputStream(file.getInputStream())
        File destFile = _save_getDestinationFile(input)
        file.transferTo(destFile)
    }

    /**
     * Save torrent file from ByteArrayInputStream
     *
     * @param ByteArrayInputStream
     */
    public void save(ByteArrayInputStream input) {
        // Need a copy because the stream decode below moves the cursor around
        def tmpBytes = new ByteArrayInputStream(input.getBytes())
        input.reset()

        File destFile = _save_getDestinationFile(tmpBytes)
        destFile.withOutputStream { s ->
            s << input.getBytes()
        }
        input.reset()

        File verifyFile = new File(destFile.getPath())
        if (!verifyFile.exists()) {
            throw new FileNotFoundException(path)
        }
    }

    private File _save_getDestinationFile(InputStream input) {
        TorrentMetainfo metainfo = this.getTorrentMetainfoForInputStream(input)
        String destination = this._getFilePathForInfoHash(metainfo.info_hash)
        File dirPath = new File(this._getDirPathForInfoHash(metainfo.info_hash))
        if (!dirPath.exists()) {
            dirPath.mkdirs()
        }

        File destFile = new File(destination)
        if (destFile.exists()) {
            throw new IOException("File exists: " + destination)
        }
        return destFile
    }

    public void remove(String infoHash) {
        String path = this._getFilePathForInfoHash(infoHash)
        File pathFile = new File(path)
        if (!pathFile.exists()) {
            throw new FileNotFoundException(path)
        }
        if (!pathFile.delete()) {
            throw new IOException("Remove error: " + path)
        }
    }
}
